﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;

namespace csrun
{
    public class CSRun
    {
        public static void Main()
        {
            try
            {
                List<string> args = new List<string>(Environment.GetCommandLineArgs());

                if (args.IndexOf("-h") > 0 || args.Count == 1)
                {
                    // Display help
                    Console.WriteLine();
                    Console.WriteLine("CSRun - C# Script Runner");
                    Console.WriteLine("   © 1989-" + DateTime.Now.Year.ToString() + " Sector Micro Computer, Inc.");
                    Console.WriteLine("");
                    Console.WriteLine("   EXAMPLE:  csrun CLASSFILE -c CLASSNAME -m MAINMETHOD -n NAMESPACE -r System.Core.dll,C:\\Libraries\\SMC.dll");
                    return;
                }
                string sFile = args[1];
                string sMethod = args[args.IndexOf("-m") + 1];
                string sClass = args[args.IndexOf("-c") + 1];
                string sNamespace = args[args.IndexOf("-n") + 1];
                if (!System.IO.File.Exists(sFile)) throw new System.IO.FileNotFoundException("Could not find file.", sFile);
                List<string> references = new List<string>(args[args.IndexOf("-r") + 1].Split(','));
                CodeCompiler compiler = new CodeCompiler();
                compiler.ExecuteCode(System.IO.File.ReadAllText(sFile), sNamespace, sClass, sMethod, true, references);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }
    /// <summary>
    /// This class can be used to execute dynamic uncompiled code at runtime
    /// This class is not exception safe, all function calls should be exception handled.
    /// </summary>
    public class CodeCompiler
    {

        /// <summary>
        /// Default Constructor.
        /// It is necessary to have an instance of the class so that the reflection
        /// can get the namespace of this class dynamically.
        /// </summary>
        /// <remarks>
        /// This class could be static, but I wanted to make it easy for developers
        /// to use this class by allowing them to change the namespace without
        /// harming the code.
        /// </remarks>
        public CodeCompiler()
        {
        }

        /// <summary>
        /// This is the main code execution function.
        /// It allows for any compilable c# code to be executed.
        /// </summary>
        /// <param name="code">the code to be compiled then executed</param>
        /// <param name="namespacename">the name of the namespace to be executed</param>
        /// <param name="classname">the name of the class of the function in the code that you will execute</param>
        /// <param name="functionname">the name of the function that you will execute</param>
        /// <param name="isstatic">True if the function you will execute is static, otherwise false</param>
        /// <param name="args">any parameters that must be passed to the function</param>
        /// <returns>what ever the executed function returns, in object form</returns>
        public object ExecuteCode(string code, string namespacename, string classname,
            string functionname, bool isstatic, List<String> references, params object[] args)
        {
            object returnval = null;
            Assembly asm = BuildAssembly(code, references);
            object instance = null;
            Type type = null;
            if (isstatic)
            {
                type = asm.GetType(namespacename + "." + classname);
            }
            else
            {
                instance = asm.CreateInstance(namespacename + "." + classname);
                type = instance.GetType();
            }
            MethodInfo method = type.GetMethod(functionname);
            returnval = method.Invoke(instance, args);
            return returnval;
        }

        /// <summary>
        /// This private function builds the assembly file into memory after compiling the code
        /// </summary>
        /// <param name="code">C# code to be compiled</param>
        /// <returns>the compiled assembly object</returns>
        private Assembly BuildAssembly(string code, List<String> references)
        {
            Microsoft.CSharp.CSharpCodeProvider provider = new CSharpCodeProvider(new System.Collections.Generic.Dictionary<String, String> { { "CompilerVersion", "v4.0" } });
            ICodeCompiler compiler = provider.CreateCompiler();
            CompilerParameters compilerparams = new CompilerParameters();
            compilerparams.GenerateExecutable = false;
            compilerparams.GenerateInMemory = true;
            compilerparams.ReferencedAssemblies.Add("system.dll");
            compilerparams.ReferencedAssemblies.Add("system.xml.dll");
            compilerparams.ReferencedAssemblies.Add("system.data.dll");
            compilerparams.ReferencedAssemblies.Add("system.windows.forms.dll");
            compilerparams.ReferencedAssemblies.Add("system.core.dll");
            foreach (string sRef in references)
            {
                if(!compilerparams.ReferencedAssemblies.Contains(sRef.ToLower()) && (!sRef.Contains(@":\") || (sRef.Contains(@":\") && System.IO.File.Exists(sRef))))
                    compilerparams.ReferencedAssemblies.Add(sRef.ToLower());
            }

            CompilerResults results = compiler.CompileAssemblyFromSource(compilerparams, code);
            if (results.Errors.HasErrors)
            {
                StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
                foreach (CompilerError error in results.Errors)
                {
                    errors.AppendFormat("Line {0},{1}\t: {2}\n", error.Line, error.Column, error.ErrorText);
                }
                throw new Exception(errors.ToString());
            }
            else
            {
                return results.CompiledAssembly;
            }
        }
    }



}
